package com.apps.saspect.impl;

import cn.hutool.core.util.RandomUtil;
import com.apps.sannotation.service.OnSingleThread;
import com.apps.sannotation.service.enums.SingleThreadPattern;
import com.apps.saspect.enums.ThreadLockPattern;
import com.apps.saspect.model.vo.KeyValueVo;
import com.apps.saspect.utils.AspectUtils;
import com.apps.sconfig.SDevFastConfig;
import com.apps.sconfig.enums.LockType;
import com.apps.sexctption.LockException;
import com.apps.sexctption.NullException;
import com.apps.stoolkit.SpiderLockToolkit;
import com.apps.stoolkit.RedissonToolkit;
import com.apps.saspect.ThreadLock;
import com.apps.sannotation.keys.JoinKey;
import com.apps.sutils.StringUtils;
import com.apps.sworld.dao.sDao;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 自定义注解的实现,用于在方法离开时删除缓存
 */

@Slf4j
@Aspect
@Component
@Configuration
public class ThreadLockAspect {


    @Autowired(required = false)
    private SDevFastConfig sDevFastConfig;

    //锁前缀
    private String prifix="ThreadLockAspect_";

    @Pointcut("@annotation(com.apps.saspect.ThreadLock)")
    public void pointcut() {

    }

    /**
     * 只支持传参是int,long,float,string类型,自动反射生成key
     * @param joinPoint
     */
    @Around("pointcut()")
    public Object doBefore(ProceedingJoinPoint joinPoint) throws IllegalAccessException,Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        ThreadLock threadLock=method.getDeclaredAnnotation(ThreadLock.class);
        List<KeyValueVo> params= AspectUtils.handlerParameter(joinPoint,JoinKey.class); //获取方法的参数传值
        if(null == threadLock)
            throw new NullException("没有找到@ThreadLock注解");
        RLock  lock = null;
        String keys=findKey(joinPoint,method,threadLock.patten());
        //没找到key就直接返回
        if(StringUtils.isEmpty(keys))
            return joinPoint.proceed();
        //用参数拼装key
        keys += AspectUtils.generateKey(params);
        //判断锁类型
        if(sDevFastConfig.getLockType() == LockType.LOCAL ) {
            if(!SpiderLockToolkit.tryLock(keys,threadLock.trytime(), threadLock.releasetime(), TimeUnit.SECONDS))
                throw new LockException(threadLock.errMsg());
        }else if(sDevFastConfig.getLockType() == LockType.REDISSON )
        {
            //加锁
            lock = RedissonToolkit.getRLock(keys);
            try {
                boolean bs = lock.tryLock(threadLock.trytime(), threadLock.releasetime(), TimeUnit.SECONDS);
                //如果获取锁失败就再尝试10次
                for (int i = 0; i < threadLock.trynum() && !bs; i++) {
                    bs = lock.tryLock(threadLock.trytime(), threadLock.releasetime(), TimeUnit.SECONDS);
                    Thread.sleep(RandomUtil.randomLong(300, 1000));
                }
                //还是获取锁失败就只能报错了
                if (!bs)
                    throw new LockException(threadLock.errMsg());

            } catch (Exception e)
            {
                log.error("", e);
                lock.unlock();
            }
        }

        //加锁成功了则真实执行方法
        Object result = null;
        try
        {
            result = joinPoint.proceed();
        }catch (Exception e)
        {
            //发生异常了释放锁
            unLock(keys);
            throw e;
        }

        //执行成功释放锁
        unLock(keys);
        return result;
    }

    /**
     * 释放锁
     * @param keys
     */
    protected void unLock(String keys)
    {
        if(sDevFastConfig.getLockType() == LockType.LOCAL) {
            SpiderLockToolkit.unlock(keys);
        }else if(sDevFastConfig.getLockType() == LockType.REDISSON)
        {
            RLock lock=RedissonToolkit.getRLock(keys);
            if (null != lock && (lock.isLocked() && lock.isHeldByCurrentThread()))
                lock.unlock();
        }
    }

    /**
     * 寻找key
     * @param joinPoint
     * @param method
     * @return
     */
    @SneakyThrows
    protected String findKey(ProceedingJoinPoint joinPoint,Method method,ThreadLockPattern pattern)
    {
        String result=prifix + joinPoint.getTarget().getClass().getSimpleName()+"_";

        //如果是必须匹配OnSingleThread的模式
        if(pattern == ThreadLockPattern.OnSingleThread)
        {
            //如果该方法的类上注解了开启整张表的单线程锁
            OnSingleThread singleThread=joinPoint.getTarget().getClass().getAnnotation(OnSingleThread.class);
            //找不到OnSingleThread这个注解就直接返回不加锁
            if (null == singleThread)
                return null;

            //如果是保存模式且该方法不是保存方法则直接返回
            if (singleThread.pattern() == SingleThreadPattern.Save && method.getName().indexOf("save") == -1)
                 return null;
            //如果是更新模式且该方法不是更新方法则直接返回
            else if (singleThread.pattern() == SingleThreadPattern.Update && method.getName().indexOf("update") == -1)
                 return null;

            sDao dao= (sDao) joinPoint.getTarget();
            //如果是保存和更新都加锁,则用类名和表名
            if(singleThread.pattern() == SingleThreadPattern.SaveAndUpdate)
                result += dao.currentModelClass().getSimpleName();
            else
                //用类名表名和方法名做key加锁
                result += dao.currentModelClass().getSimpleName() + "_" + method.getName()+"_";
        }else
        {
            //如果不是，则按正常情况进行加锁
            result += method.getName() + "_";
        }

        return result;

    }

}
